package net.net23.sysdent.sql;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;

import net.net23.sysdent.sql.exception.DataBaseConnectionException;

public class DBEngine {

	private static Connection connection;

	/**
	 * Establece la conexión a la base de datos
	 * 
	 * @param properties
	 *            Las propiedades de la conexión a la base de datos
	 * @throws DataBaseConnectionException
	 *             Si no se establecieron las propiedades mínimas: Driver y URL
	 * @throws ClassNotFoundException
	 *             Si no se encontró la clase del driver en el classpath
	 * @throws SQLException
	 *             Si ocurre algún problema al establecer la conexión a la Base
	 *             de datos
	 */
	public static void createConnection(Properties properties)
			throws DataBaseConnectionException, ClassNotFoundException,
			SQLException {
		String url = properties.getProperty("url");
		String driver = properties.getProperty("driver");
		validateFields(url, driver);
		if (connection == null) {
			connection = getConnection(driver, url, properties);
			try {
				String readOnly = properties.getProperty("readOnly");
				connection.setReadOnly("1".equals(readOnly));
//				System.out.println("Conexión creada con readOnly:" + readOnly);
			} catch (Exception e) {
				e.printStackTrace();
			}

			try {
				String autoCommit = properties.getProperty("autoCommit");
				connection.setAutoCommit("1".equals(autoCommit));
//				System.out.println("Conexión creada con autoCommit:"
//						+ autoCommit);
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
				properties.store(new FileOutputStream(new File("connection.properties")), null);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Cierra la conexión a la base de datos.
	 * 
	 * @throws SQLException
	 */
	public static void closeConnection() throws SQLException {
		if (connection != null) {
			connection.close();
			connection = null;
		}
	}

	/**
	 * Valida los campos url y driver como obligatorios para la creación de la
	 * conexión.
	 * 
	 * @param url
	 *            La url de conexión.
	 * @param driver
	 *            El nombre de la clase del driver de conexión.
	 * @throws DataBaseConnectionException
	 *             Si alguno de los campos no se especificó.
	 */
	private static void validateFields(String url, String driver)
			throws DataBaseConnectionException {
		if (url == null || url.trim().length() == 0) {
			throw new DataBaseConnectionException(
					"La url de conexión es obligatoria");
		}
		if (driver == null || driver.trim().length() == 0) {
			throw new DataBaseConnectionException(
					"El driver JDBC es olbigatorio");
		}
	}

	/**
	 * Establece la conexión a la Base de Datos
	 * 
	 * @param url
	 *            La url de conexión.
	 * @param driver
	 *            El nombre de la clase del driver de conexión.
	 * @param properties
	 *            Las propiedades adicionales para la conexión
	 * @return La conexión a la Base de Datos
	 * @throws ClassNotFoundException
	 *             Si no se encontró el driver.
	 * @throws SQLException
	 *             Si ocurrió un error al tratar de conectarse.
	 */
	private static Connection getConnection(String driver, String url,
			Properties properties) throws ClassNotFoundException, SQLException {
		Class.forName(driver);
		return DriverManager.getConnection(url, properties);
	}

	/**
	 * Ejecuta un sentencia SQL con los parametros indicados
	 * 
	 * @param sql
	 *            La sentencia SQL a ejecutar
	 * @param params
	 *            Los parametros a reemplazar en la consulta
	 * @return Un objeto DBSet que contiene el resultado de la ejecución, Este
	 *         objeto se cierra una vez finaliza de recorrer los registros.
	 * @throws SQLException
	 *             Si ocurre un problema en la ejecución de la consulta.
	 * @throws DataBaseConnectionException
	 */
	public static DBSet executeQuery(String sql, String... params)
			throws SQLException, DataBaseConnectionException {
		PreparedStatement prepareStatement = executeNativeQuery(sql, params);
		return new DBSet(prepareStatement);
	}

	public static PreparedStatement executeNativeQuery(String sql, String... params)
			throws DataBaseConnectionException, SQLException {
		if (connection == null) {
			throw new DataBaseConnectionException(
					"Debe realizar la conexión antes de ejecutar consultas.");
		}
		PreparedStatement prepareStatement = connection.prepareStatement(sql);
		int i = 1;
		for (String param : params) {
			prepareStatement.setString(i++, param);
		}
		return prepareStatement;
	}

	public static Object[] listDrivers() {
		Enumeration<Driver> drivers = DriverManager.getDrivers();
		ArrayList<Object> _drivers = new ArrayList<Object>();
		while (drivers.hasMoreElements()) {
			Driver driver = (Driver) drivers.nextElement();
			_drivers.add(driver.getClass().getName());
		}
		Object[] result = new Object[_drivers.size()];
		return _drivers.toArray(result);
	}
}
